package org.carabiner.infinitest.depgraph;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import org.carabiner.infinitest.InfinitestUtils;
import org.junit.Test;
import org.junit.internal.runners.TestIntrospector;

/**
 * The <code>JavaClass</code> class represents a Java class or interface.
 * 
 * @author <b>Mike Clark</b>
 * @author Clarkware Consulting, Inc.
 * @author <a href="mailto:benrady@gmail.com"Ben Rady</a>
 */

public class JavaClass
{
  private String className;
  private boolean isAbstract;
  private Set<String> classImports;
  private Set<JavaClass> parents;

  public JavaClass(String name)
  {
    className = name;
    isAbstract = false;
    parents = new HashSet<JavaClass>();
    classImports = new HashSet<String>();
  }

  public void setName(String name)
  {
    className = name;
  }

  public String getName()
  {
    return className;
  }

  public void addImport(String classname)
  {
    try
    {
      if (classname.startsWith("["))
      {
        Class clazz = Class.forName(className);
        // Normalize the name if it's weird.
        classname = clazz.getName();
      }
      classImports.add(classname);
    }
    catch (ClassNotFoundException e)
    {
      throw new IllegalArgumentException(classname + " is not on the classpath");
    }
  }

  public boolean isAbstract()
  {
    return isAbstract;
  }

  public void isAbstract(boolean isAbstract)
  {
    this.isAbstract = isAbstract;
  }

  public boolean equals(Object other)
  {

    if (other instanceof JavaClass)
    {
      JavaClass otherClass = (JavaClass) other;
      return otherClass.getName().equals(getName());
    }

    return false;
  }

  public int hashCode()
  {
    return getName().hashCode();
  }

  /**
   * Gets the collection on classes that this class depends on. i.e. the list of this classes
   * children.
   * 
   */
  public Collection<String> getImports()
  {
    return Collections.unmodifiableCollection(classImports);
  }

  /**
   * A parent class is one that depends on this class. i.e. if this class changes, it's parent's
   * behavior may change.
   * 
   */
  public Set<JavaClass> getParents()
  {
    return parents;
  }

  /**
   * A parent class is one that depends on this class. i.e. if this class changes, it's parent's
   * behavior may change.
   * 
   * @param clazz
   */
  public void addParent(JavaClass clazz)
  {
    parents.add(clazz);
  }

  @Override
  public String toString()
  {
    return getName();
  }

  public boolean isTest()
  {
    if (isAbstract())
      return false;

    Class clazz = loadClass(getName());
    if (!isATest(clazz))
      return false;
    return true;
  }

  private Class loadClass(String classname)
  {
    try
    {
      ClassLoader loader = new CustomClassLoader();
      return loader.loadClass(getName());
    }
    catch (ClassNotFoundException e)
    {
      throw new IllegalArgumentException(classname + " is not on the classpath");
    }
  }

  private boolean isATest(Class clazz)
  {
    if (clazz == null)
      return false;
    if (isJUnit4TestCase(clazz))
      return true;
    if (clazz.getSuperclass() == null)
      return false;
    if (isJUnit3TestCase(clazz))
      return true;
    return false;
  }

  private boolean isJUnit4TestCase(Class clazz)
  {
    TestIntrospector introspector = new TestIntrospector(clazz);
    return introspector.getTestMethods(Test.class).size() > 0;
  }

  public static boolean isJUnit3TestCase(Class clazz)
  {
    return junit.framework.TestCase.class.isAssignableFrom(clazz);
  }

  private class CustomClassLoader extends ClassLoader
  {
    public CustomClassLoader()
    {
      super();
    }

    @SuppressWarnings("unchecked")
    public Class loadClass(String className) throws ClassNotFoundException
    {
      return findClass(className);
    }

    @SuppressWarnings("unchecked")
    public Class findClass(String className)
    {
      try
      {
        String classPath = ((String) "/" + ClassLoader.getSystemResource(className.replace('.', File.separatorChar) + ".class").getFile()).substring(1);
        byte[] classByte = loadClassData(classPath);
        Class result = defineClass(className, classByte, 0, classByte.length, null);
        InfinitestUtils.log(className + ":" + result.getSuperclass());
        return result;
      }
      catch (IOException e)
      {
        // HACK Swallowing exception - How else do we find out if the classloader will be able to sucessfully load the class?
        InfinitestUtils.log(Level.WARNING, "Cannot load class " + className);
      }

      try
      {
        return findSystemClass(className);
      }
      catch (ClassNotFoundException e)
      {
        throw new IllegalArgumentException("Cannot load class " + className, e);
      }

    }

    private byte[] loadClassData(String className) throws IOException
    {

      File f;
      f = new File(className);
      int size = (int) f.length();
      byte buff[] = new byte[size];
      FileInputStream fis = new FileInputStream(f);
      DataInputStream dis = new DataInputStream(fis);
      dis.readFully(buff);
      dis.close();
      fis.close();
      return buff;
    }

  }
}
